var _byLength = function (a, b){
    return b.length - a.length;
};
var _defaultDelims = ['\r','\n', '\r\n'];
function LineTracker(textModel){
    this.$initEventable();
    this._initDelimiter();
    this._model = textModel;
    this.$on(this._model, 'Changed', this._onModelChanged);
    this._lineBreaksDelimiter = [];
    this._lineBreaksOffset = [];
    this._update(0, 0, this._model.getText());
}

LineTracker.prototype = {
    _initDelimiter: function (){
        var delims = ['\r','\n', '\r\n'];
        delims.sort(_byLength);
        // max length of delimiters.
        this._delen = delims[0].length;
        for (var i=0; i<delims.length; i++) {
            delims[i] = Utils.stringEscape(delims[i]);
        }
        this._regex = new RegExp(delims.join('|'), 'g');
    },
    _update: function (offset, deletedLength, insertedText){
        var headLen, tailLen;
        var startLineIndex = this.getLineIndex(offset);
        if (offset > this.getLineEndOffset(startLineIndex)) {
            headLen = Math.max(this._delen - 1,
                offset - this.getLineEndOffset(startLineIndex));
        }
        var endOffset = offset + deletedLength;
        var endLineIndex = this.getLineIndex(endOffset);
        if (endOffset > this.getLineEndOffset(endLineIndex)) {
            tailLen = Math.max(this._delen - 1,
                this.getLineEndOffset(endLineIndex, true) - endOffset);
        }
        var startBreakIndex = headLen ? startLineIndex - 1 : startLineIndex;
        var endBreakIndex = tailLen ? endLineIndex + 1 : endLineIndex;
        var buff = [];
        if (headLen) {
            buff.push(this._model.getText(offset - headLen, offset));
        }
        buff.push(insertedText);
        if (tailLen) {
            buff.push(this._model.getText(offset + insertedText.length, offset + insertedText.length + tailLen));
        }
        var text = buff.join('');
        var offsets = [];
        var delimiters = [];
        var m, regex = this._regex;
        regex.lastIndex = 0;
        while (m = regex.exec(text)) {
            offsets.push(m.index);
            delimiters.push(m[0]);
        }

        var array = [startBreakIndex,  endBreakIndex - startBreakIndex];
        array.push.apply(array, delimiters);
        array.splice.apply(this._lineBreaksDelimiter, array);
        array = [startBreakIndex,  endBreakIndex - startBreakIndex];
        array.push.apply(array, offsets);
        array.splice.apply(this._lineBreaksOffset, array);
        var newBreakIndex = startBreakIndex + delimiters.length;
        var delta = insertedText.length - deletedLength;
        var k = this._lineBreaksOffset.length;
        while (k -- > newBreakIndex) {
            this._lineBreaksOffset[k] += delta;
        }
    },
    _onModelChanged: function (evt){
        var offset = evt.offset;
        var deletedLength = evt.deletedText.length;
        var insertedText = evt.insertedText;
        this._update(offset, deletedLength, insertedText);
    },
    getLineCount: function (index){
        return this._lineBreaksDelimiter.length + 1;
    },
    getLineIndex: function (offset){
        var left = 0;
        var right = this.getLineCount();
        while (left < right) {
            var middle = (left + right) >> 1;
            var startOffset = this.getLineStartOffset(middle);
            if (startOffset < offset) {
                left = middle + 1;
            } else if (startOffset == offset) {
                left = right = middle;
            } else {
                right = middle;
            }
        }
        return left;
    },
    getLineStartOffset: function (index){
        return index == 0 ? 0 : this.getLineEndOffset(index - 1, true);
    },
    getLineEndOffset: function (index, includeDelimiter){
        if (index == this._lineBreaksDelimiter.length) {
            return this._model.getLength();
        }
        var offset = this._lineBreaksOffset[index];
        if (includeDelimiter) {
            offset += this.getLineDelimiter(index).length;
        }
        return offset;
    },
    getLineDelimiter: function (index){
        return this._lineBreaksDelimiter[index] || '';
    }
};

Utils.extend(LineTracker, Eventable);